package ldh.text.service;

import ldh.text.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 此方法有性能问题
 */
public class Cache2DataService implements CursorDataService<ByteBuffer> {

    private static final Logger LOGGER = LoggerFactory.getLogger(Cache2DataService.class);

    private long startPosition = 0;
    private long endPosition = 0;
    private int cacheSize;
    private File file;
    private long fileSize;

    private RandomAccessFile randomAccessFile = null;
    private FileChannel fileChannel;
    private ByteBuffer byteBuffer;
    private byte[] cacheBytes;

    private int currentRegion = 0;
    private int currentRegionPosition = 0;

    public Cache2DataService(File file, int cacheSize) throws IOException {
        this.file = file;
        this.cacheSize = cacheSize;
        initFileInput();
    }

    private void initFileInput() throws IOException {
        randomAccessFile = new RandomAccessFile(file, "r");
        fileChannel = randomAccessFile.getChannel();
        fileSize = fileChannel.size();
        cacheBytes = new byte[cacheSize];
//        bufferCount = (int) Math.ceil((double)fileSize/(double)Integer.MAX_VALUE);
        if (fileSize > Integer.MAX_VALUE) {
            byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, Integer.MAX_VALUE);
        } else {
            byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
        }
    }

    @Override
    public long dataSize() {
        return fileSize;
    }

    @Override
    public ByteBuffer loadData(long position, int size) throws IOException {
        if (position >= this.dataSize()) {
           return ByteBuffer.wrap(new byte[0]);
        }
        if (size > cacheSize) {
            throw new IllegalArgumentException("获取数据长度不能超过缓存大小");
        }
        if (position < 0 || size < 0) {
            throw new IllegalArgumentException("不能小于0");
        }
        if (position + size > dataSize()) {
            size = (int)(dataSize() - position);
        }
        ByteBuffer data = loadBytesData(position, size);
        int length = data.limit();
        if (length == 0L) return data;
        if (position > endPosition) {
            this.startPosition = position;
            this.endPosition = position + length;
        } else if (position == endPosition) {
            if (endPosition - startPosition + length > cacheSize) {
                startPosition += (endPosition - startPosition + length - cacheSize);
                endPosition += length;
            } else {
                endPosition += length;
            }
        } else if (position >= startPosition) {
            startPosition = position;
            endPosition = position + length;
        } else if (position < startPosition) {
            if (position + length == startPosition) {
                if (endPosition - startPosition + length > cacheSize) {
                    startPosition -= length;
                    endPosition -= (endPosition - startPosition + length - cacheSize);
                } else {
                    startPosition -= length;
                }
            } else {
                startPosition = position;
                endPosition = position + length;
            }
        }
        return data;
    }

    @Override
    public void saveData(String file, byte[] data, int startPosition, int endPosition) {

    }

    @Override
    public void saveData(File file, byte[] data) throws IOException {

    }

    private ByteBuffer loadBytesData(long position, int size) throws IOException {
        if (position >= fileSize) return ByteBuffer.wrap(new byte[0]);
        if (position + size > fileSize) {
            size = (int) (fileSize - position);
        }

        long st = System.currentTimeMillis();
        if (endPosition == position) {
            if (Integer.MAX_VALUE - currentRegionPosition < size) {
                int size1 = Integer.MAX_VALUE - currentRegionPosition;
                int size2 = (int) (currentRegionPosition - Integer.MAX_VALUE + size);
                byteBuffer.get(cacheBytes, 0, size1);

                currentRegion++;
                initNewMap();

                byteBuffer.get(cacheBytes, size1, size2);
                currentRegionPosition = size2;
                int length = StringUtil.lastedLine(cacheBytes, size1+size2);
                if (length > size1) {
                    currentRegionPosition = length-size1;
                } else {
                    currentRegionPosition = size2;
                    length = size1 + size2;
                }

                if (length != size2) {
                    byteBuffer.position(currentRegionPosition);
                }
                LOGGER.info("region {}, time1 {}",  currentRegion, System.currentTimeMillis() - st);
                return ByteBuffer.wrap(cacheBytes, 0, size);
            } else {
                byteBuffer.get(cacheBytes, 0, size);
                int length = StringUtil.lastedLine(cacheBytes, size);
                currentRegionPosition += length;
                if (length != size) {
                    byteBuffer.position(currentRegionPosition);
                }
                currentRegionPosition += size;
                LOGGER.info("region {}, time2 {}",  currentRegion, System.currentTimeMillis() - st);
                return ByteBuffer.wrap(cacheBytes, 0, size);
            }
        } else {
            int region = (int)(position / Integer.MAX_VALUE);
            long moreSize = position - region * (long)Integer.MAX_VALUE;
            this.currentRegion = region + 1;
            initNewMap();
            if (moreSize + size > Integer.MAX_VALUE) {
                int size1 = Integer.MAX_VALUE - (int)moreSize;
                int size2 = (int) (moreSize - Integer.MAX_VALUE + size);
                byteBuffer.position((int)moreSize);
                byteBuffer.get(cacheBytes, 0, size1);
                currentRegion++;
                initNewMap();

                byteBuffer.get(cacheBytes, size1, size2);
                int length = StringUtil.lastedLine(cacheBytes, size1+size2);
                LOGGER.info("region {}, time3 {}",  currentRegion, System.currentTimeMillis() - st);
                return ByteBuffer.wrap(cacheBytes, 0, length);
            } else {
                byteBuffer.position((int)moreSize);
                byteBuffer.get(cacheBytes, 0, size);
                int length = StringUtil.lastedLine(cacheBytes, size);
                LOGGER.info("region {}, time4 {}",  currentRegion, System.currentTimeMillis() - st);
                return ByteBuffer.wrap(cacheBytes, 0, length);
            }
        }
    }

    @Override
    public ByteBuffer loadNextData(int size) throws IOException {
        if (endPosition == dataSize()) return ByteBuffer.wrap(new byte[0]);
        return loadData(endPosition, size);
    }

    @Override
    public ByteBuffer loadPreData(int size) throws IOException {
        if (startPosition == 0) {
            return ByteBuffer.wrap(new byte[0]);
        } else if (startPosition - size < 0) {
            if (size > endPosition) {
                return loadData(0, (int)endPosition);
            } else {
                return loadData(0, size);
            }
        }
        return loadData(startPosition-size, size);
    }

    @Override
    public long getStartPosition() {
        return startPosition;
    }

    @Override
    public long getEndPosition() {
        return endPosition;
    }

    @Override
    public void close() throws IOException {
        byteBuffer.clear();
        if (fileChannel != null) {
            fileChannel.close();
        }
        if (randomAccessFile != null) {
            randomAccessFile.close();
        }
    }

    private void initNewMap() throws IOException {
        long position = (long) Integer.MAX_VALUE * currentRegion;
        if (position + Integer.MAX_VALUE < fileSize) {
            byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, Integer.MAX_VALUE);
        } else {
            byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, fileSize - position);
        }

    }
}
